Today it is almost impossible to get you player data
like screenshots or Forge map variants from classic
Xbox 360 Halo 3 to you PC. This program solves this
-problem. The reclaimer currently exports:
+problem. The Reclaimer currently exports:
- player screenshots
- Forge map variants
+- Custom game types
BUILDING FORM SOURCE:
- Even tough I am not a supporter of distributing software
+ Even though I am not a supporter of distributing software
in already built form, I do so for the comfort of target
audience of this specific program. So unless you want to
change some suff in the code, YOU CAN SKIP THIS PROCEDURE.
Otherwise, there is a build.xml file configured for your
- (and mine) convinience which requires build tool Apache Ant.
+ (and mine) convinience, which requires build tool Apache Ant.
https://ant.apache.org/index.html
To build the program:
1. Install Ant
2. Inspect the 'build.xml' in root directory
- 3. Inspect the code in src to see what it does
+ 3. Inspect the code in src/ to see what it does
4. Then simply run:
ant all
HOW TO USE
- See user manual in 'Doc/user-manual.pdf'.
+ See user manual in doc/user-manual.pdf.
RELEASE NOTES:
0.1 - Screenshots extracting
0.2 - Forge maps extracting
+ 0.3 - Custom game types extracting
+
+
+
+TO DO:
+ 1.0 - Films extracting
+ 1.? - Campaign saves extracting
version="true"
use="true"
windowtitle="Halo 3 Reclaimer"
- doctitle="Halo 3 Reclaimer - File extractor from Xbox 360 version of Halo 3"
- bottom="Copyright 2025. Documentation licensed under GFDL v1.3."
+ doctitle="Halo 3 Reclaimer - player data extractor from Xbox 360 version of Halo 3"
+ bottom="Copyright 2025, 2026. Documentation licensed under GFDL v1.3."
overview="overview.html">
<fileset dir="${src.dir}">
<include name="*.java"/>
<!-- Build everything -->
<target name="all" depends="clean,jar,javadoc"/>
-</project>
\ No newline at end of file
+</project>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Arbiter (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
Cortana first, which copies the Halo 3 files out of the Xbox file system;
other files on the stick are ignored then.</p>
- <p>Halo 3 keeps every screenshot and every Forge map in its own package,
- so the Arbiter looks inside each file: a package holding
+ <p>Halo 3 keeps every screenshot, Forge map and custom game type in its own
+ package, so the Arbiter looks inside each file: a package holding
<code>screen.shot</code> is a screenshot, one holding <code>sandbox.map</code>
- is a map. Screenshots are checked first, then maps. Anything else is skipped.
- Only when the content says nothing (DDS, TGA, raw textures) the file name
- decides, like in version 1.0.</p>
+ is a map and one holding <code>variant.slayer</code>, <code>variant.ctf</code>...
+ is a game mode. Screenshots are checked first, then maps, then game modes.
+ Anything else is skipped. Only when the content says nothing (DDS, TGA,
+ raw textures) the file name decides, like in version 1.0.</p>
- <p>Results go to two folders in the output directory:
- <code>screenshots-reclaimed</code> and <code>maps-reclaimed</code>.</p></div>
+ <p>Results go to <code>halo-3-reclaimed-files</code> in the output directory,
+ sorted into <code>screenshots</code>, <code>maps</code> and <code>game-modes</code>.
+ Files are named after their in-game names, see <a href="Arbiter.html#saveFiles-java.io.File-java.lang.String-java.lang.String-java.util.Map-"><code>saveFiles(java.io.File, java.lang.String, java.lang.String, java.util.Map<java.lang.String, byte[]>)</code></a>.</p></div>
<dl>
<dt><span class="simpleTagLabel">Version:</span></dt>
-<dd>1.0</dd>
+<dd>1.1</dd>
</dl>
</li>
</ul>
</tr>
<tr class="altColor">
<td class="colFirst"><code>static java.lang.String</code></td>
+<td class="colLast"><code><span class="memberNameLink"><a href="Arbiter.html#GAME_MODES_DIR">GAME_MODES_DIR</a></span></code> </td>
+</tr>
+<tr class="rowColor">
+<td class="colFirst"><code>static java.lang.String</code></td>
<td class="colLast"><code><span class="memberNameLink"><a href="Arbiter.html#MAPS_DIR">MAPS_DIR</a></span></code> </td>
</tr>
+<tr class="altColor">
+<td class="colFirst"><code>static java.lang.String</code></td>
+<td class="colLast"><code><span class="memberNameLink"><a href="Arbiter.html#OUTPUT_DIR">OUTPUT_DIR</a></span></code> </td>
+</tr>
<tr class="rowColor">
<td class="colFirst"><code>static java.lang.String</code></td>
<td class="colLast"><code><span class="memberNameLink"><a href="Arbiter.html#SCREENSHOTS_DIR">SCREENSHOTS_DIR</a></span></code> </td>
<td class="colFirst"><code>static java.lang.String</code></td>
<td class="colLast"><code><span class="memberNameLink"><a href="Arbiter.html#processFile-java.io.File-java.io.File-">processFile</a></span>(java.io.File input,
java.io.File outputDir)</code>
-<div class="block">Reclaims all screenshots and maps found in a file or folder.</div>
+<div class="block">Reclaims all screenshots, maps and game modes found in a file or folder.</div>
</td>
</tr>
</table>
<!-- -->
</a>
<h3>Field Detail</h3>
+<a name="OUTPUT_DIR">
+<!-- -->
+</a>
+<ul class="blockList">
+<li class="blockList">
+<h4>OUTPUT_DIR</h4>
+<pre>public static final java.lang.String OUTPUT_DIR</pre>
+<dl>
+<dt><span class="seeLabel">See Also:</span></dt>
+<dd><a href="constant-values.html#Arbiter.OUTPUT_DIR">Constant Field Values</a></dd>
+</dl>
+</li>
+</ul>
<a name="SCREENSHOTS_DIR">
<!-- -->
</a>
<a name="MAPS_DIR">
<!-- -->
</a>
-<ul class="blockListLast">
+<ul class="blockList">
<li class="blockList">
<h4>MAPS_DIR</h4>
<pre>public static final java.lang.String MAPS_DIR</pre>
</dl>
</li>
</ul>
+<a name="GAME_MODES_DIR">
+<!-- -->
+</a>
+<ul class="blockListLast">
+<li class="blockList">
+<h4>GAME_MODES_DIR</h4>
+<pre>public static final java.lang.String GAME_MODES_DIR</pre>
+<dl>
+<dt><span class="seeLabel">See Also:</span></dt>
+<dd><a href="constant-values.html#Arbiter.GAME_MODES_DIR">Constant Field Values</a></dd>
+</dl>
+</li>
+</ul>
</li>
</ul>
<!-- ========= CONSTRUCTOR DETAIL ======== -->
<pre>public static java.lang.String processFile(java.io.File input,
java.io.File outputDir)
throws java.lang.Exception</pre>
-<div class="block">Reclaims all screenshots and maps found in a file or folder.</div>
+<div class="block">Reclaims all screenshots, maps and game modes found in a file or folder.</div>
<dl>
<dt><span class="returnLabel">Returns:</span></dt>
-<dd>what was reclaimed, for example "Reclaimed 2 pictures and 1 map"</dd>
+<dd>what was reclaimed, for example "Reclaimed 2 pictures, 1 map and 3 game modes"</dd>
<dt><span class="throwsLabel">Throws:</span></dt>
<dd><code>java.lang.Exception</code> - if nothing could be reclaimed</dd>
</dl>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Cortana (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>ErrorHandler (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<div class="subNav">
<ul class="navList">
<li><a href="Cortana.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
-<li><a href="GraphicalUI.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
+<li><a href="GameModeReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
</ul>
<ul class="navList">
<li><a href="index.html?ErrorHandler.html" target="_top">Frames</a></li>
<p>This class just stores error messages and prints them when needed.</p></div>
<dl>
<dt><span class="simpleTagLabel">Version:</span></dt>
-<dd>1.0</dd>
+<dd>1.1</dd>
</dl>
</li>
</ul>
<div class="subNav">
<ul class="navList">
<li><a href="Cortana.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
-<li><a href="GraphicalUI.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
+<li><a href="GameModeReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
</ul>
<ul class="navList">
<li><a href="index.html?ErrorHandler.html" target="_top">Frames</a></li>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<!-- NewPage -->
+<html lang="en">
+<head>
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
+<title>GameModeReclaimer (Halo 3 Reclaimer)</title>
+<meta name="date" content="2026-11-03">
+<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+<script type="text/javascript" src="script.js"></script>
+</head>
+<body>
+<script type="text/javascript"><!--
+ try {
+ if (location.href.indexOf('is-external=true') == -1) {
+ parent.document.title="GameModeReclaimer (Halo 3 Reclaimer)";
+ }
+ }
+ catch(err) {
+ }
+//-->
+var methods = {"i0":10};
+var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]};
+var altColor = "altColor";
+var rowColor = "rowColor";
+var tableTab = "tableTab";
+var activeTableTab = "activeTableTab";
+</script>
+<noscript>
+<div>JavaScript is disabled on your browser.</div>
+</noscript>
+<!-- ========= START OF TOP NAVBAR ======= -->
+<div class="topNav"><a name="navbar.top">
+<!-- -->
+</a>
+<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
+<a name="navbar.top.firstrow">
+<!-- -->
+</a>
+<ul class="navList" title="Navigation">
+<li><a href="overview-summary.html">Overview</a></li>
+<li><a href="package-summary.html">Package</a></li>
+<li class="navBarCell1Rev">Class</li>
+<li><a href="class-use/GameModeReclaimer.html">Use</a></li>
+<li><a href="package-tree.html">Tree</a></li>
+<li><a href="deprecated-list.html">Deprecated</a></li>
+<li><a href="index-all.html">Index</a></li>
+<li><a href="help-doc.html">Help</a></li>
+</ul>
+<div class="aboutLanguage"><b>Halo 3 Reclaimer</b></div>
+</div>
+<div class="subNav">
+<ul class="navList">
+<li><a href="ErrorHandler.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
+<li><a href="GraphicalUI.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
+</ul>
+<ul class="navList">
+<li><a href="index.html?GameModeReclaimer.html" target="_top">Frames</a></li>
+<li><a href="GameModeReclaimer.html" target="_top">No Frames</a></li>
+</ul>
+<ul class="navList" id="allclasses_navbar_top">
+<li><a href="allclasses-noframe.html">All Classes</a></li>
+</ul>
+<div>
+<script type="text/javascript"><!--
+ allClassesLink = document.getElementById("allclasses_navbar_top");
+ if(window==top) {
+ allClassesLink.style.display = "block";
+ }
+ else {
+ allClassesLink.style.display = "none";
+ }
+ //-->
+</script>
+</div>
+<div>
+<ul class="subNavList">
+<li>Summary: </li>
+<li>Nested | </li>
+<li>Field | </li>
+<li><a href="#constructor.summary">Constr</a> | </li>
+<li><a href="#method.summary">Method</a></li>
+</ul>
+<ul class="subNavList">
+<li>Detail: </li>
+<li>Field | </li>
+<li><a href="#constructor.detail">Constr</a> | </li>
+<li><a href="#method.detail">Method</a></li>
+</ul>
+</div>
+<a name="skip.navbar.top">
+<!-- -->
+</a></div>
+<!-- ========= END OF TOP NAVBAR ========= -->
+<!-- ======== START OF CLASS DATA ======== -->
+<div class="header">
+<h2 title="Class GameModeReclaimer" class="title">Class GameModeReclaimer</h2>
+</div>
+<div class="contentContainer">
+<ul class="inheritance">
+<li>java.lang.Object</li>
+<li>
+<ul class="inheritance">
+<li>GameModeReclaimer</li>
+</ul>
+</li>
+</ul>
+<div class="description">
+<ul class="blockList">
+<li class="blockList">
+<hr>
+<br>
+<pre>public class <span class="typeNameLabel">GameModeReclaimer</span>
+extends java.lang.Object</pre>
+<div class="block">Core extraction engine for custom game types (game modes).
+
+ <p>Halo 3 saves every custom game type as its own Xbox 360 package, just
+ like the Forge maps. The package holds one file named after the game's
+ rules, for example <code>variant.slayer</code> or <code>variant.ctf</code>.
+ That file is the game mode itself, all of its settings, stored in Bungie's
+ BLF format. The package and BLF readers are shared with MapReclaimer.</p>
+
+ <p>Supported input formats:</p>
+ <ul>
+ <li>STFS container files holding a Halo 3 game type</li>
+ <li>already extracted <code>variant.*</code> files (BLF)</li>
+ </ul>
+
+ <p>Two files are saved, named after the game mode (see Arbiter.saveFiles):</p>
+ <ul>
+ <li><code>team_snipers.slayer</code>: byte for byte the file the console
+ keeps inside the package, its ending tells the game type</li>
+ <li><code>team_snipers.package</code>: the untouched package, which tools
+ like Horizon or Velocity can sign for another player's profile
+ (only when the input was a package)</li>
+ </ul></div>
+<dl>
+<dt><span class="simpleTagLabel">Version:</span></dt>
+<dd>1.0</dd>
+</dl>
+</li>
+</ul>
+</div>
+<div class="summary">
+<ul class="blockList">
+<li class="blockList">
+<!-- ======== CONSTRUCTOR SUMMARY ======== -->
+<ul class="blockList">
+<li class="blockList"><a name="constructor.summary">
+<!-- -->
+</a>
+<h3>Constructor Summary</h3>
+<table class="memberSummary" border="0" cellpadding="3" cellspacing="0" summary="Constructor Summary table, listing constructors, and an explanation">
+<caption><span>Constructors</span><span class="tabEnd"> </span></caption>
+<tr>
+<th class="colOne" scope="col">Constructor and Description</th>
+</tr>
+<tr class="altColor">
+<td class="colOne"><code><span class="memberNameLink"><a href="GameModeReclaimer.html#GameModeReclaimer--">GameModeReclaimer</a></span>()</code> </td>
+</tr>
+</table>
+</li>
+</ul>
+<!-- ========== METHOD SUMMARY =========== -->
+<ul class="blockList">
+<li class="blockList"><a name="method.summary">
+<!-- -->
+</a>
+<h3>Method Summary</h3>
+<table class="memberSummary" border="0" cellpadding="3" cellspacing="0" summary="Method Summary table, listing methods, and an explanation">
+<caption><span id="t0" class="activeTableTab"><span>All Methods</span><span class="tabEnd"> </span></span><span id="t2" class="tableTab"><span><a href="javascript:show(2);">Instance Methods</a></span><span class="tabEnd"> </span></span><span id="t4" class="tableTab"><span><a href="javascript:show(8);">Concrete Methods</a></span><span class="tabEnd"> </span></span></caption>
+<tr>
+<th class="colFirst" scope="col">Modifier and Type</th>
+<th class="colLast" scope="col">Method and Description</th>
+</tr>
+<tr id="i0" class="altColor">
+<td class="colFirst"><code>void</code></td>
+<td class="colLast"><code><span class="memberNameLink"><a href="GameModeReclaimer.html#extractGameMode-java.io.File-java.io.File-">extractGameMode</a></span>(java.io.File inputFile,
+ java.io.File outputDir)</code>
+<div class="block">Extracts the game mode from an Xbox 360 file and saves it, together with
+ the package it came in, under its in-game name in the output directory.</div>
+</td>
+</tr>
+</table>
+<ul class="blockList">
+<li class="blockList"><a name="methods.inherited.from.class.java.lang.Object">
+<!-- -->
+</a>
+<h3>Methods inherited from class java.lang.Object</h3>
+<code>clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait</code></li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+</div>
+<div class="details">
+<ul class="blockList">
+<li class="blockList">
+<!-- ========= CONSTRUCTOR DETAIL ======== -->
+<ul class="blockList">
+<li class="blockList"><a name="constructor.detail">
+<!-- -->
+</a>
+<h3>Constructor Detail</h3>
+<a name="GameModeReclaimer--">
+<!-- -->
+</a>
+<ul class="blockListLast">
+<li class="blockList">
+<h4>GameModeReclaimer</h4>
+<pre>public GameModeReclaimer()</pre>
+</li>
+</ul>
+</li>
+</ul>
+<!-- ============ METHOD DETAIL ========== -->
+<ul class="blockList">
+<li class="blockList"><a name="method.detail">
+<!-- -->
+</a>
+<h3>Method Detail</h3>
+<a name="extractGameMode-java.io.File-java.io.File-">
+<!-- -->
+</a>
+<ul class="blockListLast">
+<li class="blockList">
+<h4>extractGameMode</h4>
+<pre>public void extractGameMode(java.io.File inputFile,
+ java.io.File outputDir)
+ throws java.lang.Exception</pre>
+<div class="block">Extracts the game mode from an Xbox 360 file and saves it, together with
+ the package it came in, under its in-game name in the output directory.</div>
+<dl>
+<dt><span class="throwsLabel">Throws:</span></dt>
+<dd><code>java.lang.Exception</code> - if the file cannot be read, is not a Halo 3 game mode,
+ or is damaged</dd>
+</dl>
+</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+</div>
+</div>
+<!-- ========= END OF CLASS DATA ========= -->
+<!-- ======= START OF BOTTOM NAVBAR ====== -->
+<div class="bottomNav"><a name="navbar.bottom">
+<!-- -->
+</a>
+<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
+<a name="navbar.bottom.firstrow">
+<!-- -->
+</a>
+<ul class="navList" title="Navigation">
+<li><a href="overview-summary.html">Overview</a></li>
+<li><a href="package-summary.html">Package</a></li>
+<li class="navBarCell1Rev">Class</li>
+<li><a href="class-use/GameModeReclaimer.html">Use</a></li>
+<li><a href="package-tree.html">Tree</a></li>
+<li><a href="deprecated-list.html">Deprecated</a></li>
+<li><a href="index-all.html">Index</a></li>
+<li><a href="help-doc.html">Help</a></li>
+</ul>
+<div class="aboutLanguage"><i>File extractor from Xbox 360 version of Halo 3</i></div>
+</div>
+<div class="subNav">
+<ul class="navList">
+<li><a href="ErrorHandler.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
+<li><a href="GraphicalUI.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
+</ul>
+<ul class="navList">
+<li><a href="index.html?GameModeReclaimer.html" target="_top">Frames</a></li>
+<li><a href="GameModeReclaimer.html" target="_top">No Frames</a></li>
+</ul>
+<ul class="navList" id="allclasses_navbar_bottom">
+<li><a href="allclasses-noframe.html">All Classes</a></li>
+</ul>
+<div>
+<script type="text/javascript"><!--
+ allClassesLink = document.getElementById("allclasses_navbar_bottom");
+ if(window==top) {
+ allClassesLink.style.display = "block";
+ }
+ else {
+ allClassesLink.style.display = "none";
+ }
+ //-->
+</script>
+</div>
+<div>
+<ul class="subNavList">
+<li>Summary: </li>
+<li>Nested | </li>
+<li>Field | </li>
+<li><a href="#constructor.summary">Constr</a> | </li>
+<li><a href="#method.summary">Method</a></li>
+</ul>
+<ul class="subNavList">
+<li>Detail: </li>
+<li>Field | </li>
+<li><a href="#constructor.detail">Constr</a> | </li>
+<li><a href="#method.detail">Method</a></li>
+</ul>
+</div>
+<a name="skip.navbar.bottom">
+<!-- -->
+</a></div>
+<!-- ======== END OF BOTTOM NAVBAR ======= -->
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
+</body>
+</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>GraphicalUI (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
</div>
<div class="subNav">
<ul class="navList">
-<li><a href="ErrorHandler.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
+<li><a href="GameModeReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
<li><a href="Main.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
</ul>
<ul class="navList">
logic for the application's windowed interface</p></div>
<dl>
<dt><span class="simpleTagLabel">Version:</span></dt>
-<dd>1.1</dd>
+<dd>1.2</dd>
<dt><span class="seeLabel">See Also:</span></dt>
<dd><a href="serialized-form.html#GraphicalUI">Serialized Form</a></dd>
</dl>
</div>
<div class="subNav">
<ul class="navList">
-<li><a href="ErrorHandler.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
+<li><a href="GameModeReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">Prev Class</span></a></li>
<li><a href="Main.html" title="class in <Unnamed>"><span class="typeNameLink">Next Class</span></a></li>
</ul>
<ul class="navList">
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Main (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>MapReclaimer (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<p>Unlike screenshots, the map has no magic bytes worth scanning for,
so this class reads the STFS file system properly: it walks the file
table, follows the block chain of <code>sandbox.map</code> and checks
- each block against the package hash tables.</p>
+ each block against the package hash tables. These package and BLF readers
+ are shared with the other reclaimers.</p>
<p>Supported input formats:</p>
<ul>
<li>already extracted <code>sandbox.map</code> files (BLF)</li>
</ul>
- <p>The saved file is not converted in any way. It is byte for byte the
- <code>sandbox.map</code> the console keeps inside the package.</p></div>
+ <p>Two files are saved, named after the map (see Arbiter.saveFiles):</p>
+ <ul>
+ <li><code>guardian_infection.map</code>: byte for byte the
+ <code>sandbox.map</code> the console keeps inside the package</li>
+ <li><code>guardian_infection.package</code>: the untouched package, which
+ tools like Horizon or Velocity can sign for another player's profile
+ (only when the input was a package)</li>
+ </ul></div>
<dl>
<dt><span class="simpleTagLabel">Version:</span></dt>
-<dd>1.0</dd>
+<dd>1.1</dd>
</dl>
</li>
</ul>
<td class="colFirst"><code>void</code></td>
<td class="colLast"><code><span class="memberNameLink"><a href="MapReclaimer.html#extractMap-java.io.File-java.io.File-">extractMap</a></span>(java.io.File inputFile,
java.io.File outputDir)</code>
-<div class="block">Extracts the Forge map from an Xbox 360 file and saves it as
- <input name>-reclaimed.map in the output directory.</div>
+<div class="block">Extracts the Forge map from an Xbox 360 file and saves it, together with
+ the package it came in, under the map's in-game name in the output directory.</div>
</td>
</tr>
</table>
<pre>public void extractMap(java.io.File inputFile,
java.io.File outputDir)
throws java.lang.Exception</pre>
-<div class="block">Extracts the Forge map from an Xbox 360 file and saves it as
- <input name>-reclaimed.map in the output directory.</div>
+<div class="block">Extracts the Forge map from an Xbox 360 file and saves it, together with
+ the package it came in, under the map's in-game name in the output directory.</div>
<dl>
<dt><span class="throwsLabel">Throws:</span></dt>
<dd><code>java.lang.Exception</code> - if the file cannot be read, is not a Halo 3 Forge map,
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>ScreenshotReclaimer (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
</ul></div>
<dl>
<dt><span class="simpleTagLabel">Version:</span></dt>
-<dd>1.0</dd>
+<dd>1.1</dd>
</dl>
</li>
</ul>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>TerminalUI (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<p>This class contains all the command-line interface logic.</p></div>
<dl>
<dt><span class="simpleTagLabel">Version:</span></dt>
-<dd>1.0</dd>
+<dd>1.2</dd>
</dl>
</li>
</ul>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>All Classes (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<li><a href="Arbiter.html" title="class in <Unnamed>" target="classFrame">Arbiter</a></li>
<li><a href="Cortana.html" title="class in <Unnamed>" target="classFrame">Cortana</a></li>
<li><a href="ErrorHandler.html" title="class in <Unnamed>" target="classFrame">ErrorHandler</a></li>
+<li><a href="GameModeReclaimer.html" title="class in <Unnamed>" target="classFrame">GameModeReclaimer</a></li>
<li><a href="GraphicalUI.html" title="class in <Unnamed>" target="classFrame">GraphicalUI</a></li>
<li><a href="Main.html" title="class in <Unnamed>" target="classFrame">Main</a></li>
<li><a href="MapReclaimer.html" title="class in <Unnamed>" target="classFrame">MapReclaimer</a></li>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>All Classes (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<li><a href="Arbiter.html" title="class in <Unnamed>">Arbiter</a></li>
<li><a href="Cortana.html" title="class in <Unnamed>">Cortana</a></li>
<li><a href="ErrorHandler.html" title="class in <Unnamed>">ErrorHandler</a></li>
+<li><a href="GameModeReclaimer.html" title="class in <Unnamed>">GameModeReclaimer</a></li>
<li><a href="GraphicalUI.html" title="class in <Unnamed>">GraphicalUI</a></li>
<li><a href="Main.html" title="class in <Unnamed>">Main</a></li>
<li><a href="MapReclaimer.html" title="class in <Unnamed>">MapReclaimer</a></li>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class Arbiter (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class Cortana (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class ErrorHandler (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<!-- NewPage -->
+<html lang="en">
+<head>
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
+<title>Uses of Class GameModeReclaimer (Halo 3 Reclaimer)</title>
+<meta name="date" content="2026-11-03">
+<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+<script type="text/javascript" src="../script.js"></script>
+</head>
+<body>
+<script type="text/javascript"><!--
+ try {
+ if (location.href.indexOf('is-external=true') == -1) {
+ parent.document.title="Uses of Class GameModeReclaimer (Halo 3 Reclaimer)";
+ }
+ }
+ catch(err) {
+ }
+//-->
+</script>
+<noscript>
+<div>JavaScript is disabled on your browser.</div>
+</noscript>
+<!-- ========= START OF TOP NAVBAR ======= -->
+<div class="topNav"><a name="navbar.top">
+<!-- -->
+</a>
+<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
+<a name="navbar.top.firstrow">
+<!-- -->
+</a>
+<ul class="navList" title="Navigation">
+<li><a href="../overview-summary.html">Overview</a></li>
+<li><a href="../package-summary.html">Package</a></li>
+<li><a href="../GameModeReclaimer.html" title="class in <Unnamed>">Class</a></li>
+<li class="navBarCell1Rev">Use</li>
+<li><a href="../overview-tree.html">Tree</a></li>
+<li><a href="../deprecated-list.html">Deprecated</a></li>
+<li><a href="../index-all.html">Index</a></li>
+<li><a href="../help-doc.html">Help</a></li>
+</ul>
+<div class="aboutLanguage"><b>Halo 3 Reclaimer</b></div>
+</div>
+<div class="subNav">
+<ul class="navList">
+<li>Prev</li>
+<li>Next</li>
+</ul>
+<ul class="navList">
+<li><a href="../index.html?class-use/GameModeReclaimer.html" target="_top">Frames</a></li>
+<li><a href="GameModeReclaimer.html" target="_top">No Frames</a></li>
+</ul>
+<ul class="navList" id="allclasses_navbar_top">
+<li><a href="../allclasses-noframe.html">All Classes</a></li>
+</ul>
+<div>
+<script type="text/javascript"><!--
+ allClassesLink = document.getElementById("allclasses_navbar_top");
+ if(window==top) {
+ allClassesLink.style.display = "block";
+ }
+ else {
+ allClassesLink.style.display = "none";
+ }
+ //-->
+</script>
+</div>
+<a name="skip.navbar.top">
+<!-- -->
+</a></div>
+<!-- ========= END OF TOP NAVBAR ========= -->
+<div class="header">
+<h2 title="Uses of Class GameModeReclaimer" class="title">Uses of Class<br>GameModeReclaimer</h2>
+</div>
+<div class="classUseContainer">No usage of GameModeReclaimer</div>
+<!-- ======= START OF BOTTOM NAVBAR ====== -->
+<div class="bottomNav"><a name="navbar.bottom">
+<!-- -->
+</a>
+<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
+<a name="navbar.bottom.firstrow">
+<!-- -->
+</a>
+<ul class="navList" title="Navigation">
+<li><a href="../overview-summary.html">Overview</a></li>
+<li><a href="../package-summary.html">Package</a></li>
+<li><a href="../GameModeReclaimer.html" title="class in <Unnamed>">Class</a></li>
+<li class="navBarCell1Rev">Use</li>
+<li><a href="../overview-tree.html">Tree</a></li>
+<li><a href="../deprecated-list.html">Deprecated</a></li>
+<li><a href="../index-all.html">Index</a></li>
+<li><a href="../help-doc.html">Help</a></li>
+</ul>
+<div class="aboutLanguage"><i>File extractor from Xbox 360 version of Halo 3</i></div>
+</div>
+<div class="subNav">
+<ul class="navList">
+<li>Prev</li>
+<li>Next</li>
+</ul>
+<ul class="navList">
+<li><a href="../index.html?class-use/GameModeReclaimer.html" target="_top">Frames</a></li>
+<li><a href="GameModeReclaimer.html" target="_top">No Frames</a></li>
+</ul>
+<ul class="navList" id="allclasses_navbar_bottom">
+<li><a href="../allclasses-noframe.html">All Classes</a></li>
+</ul>
+<div>
+<script type="text/javascript"><!--
+ allClassesLink = document.getElementById("allclasses_navbar_bottom");
+ if(window==top) {
+ allClassesLink.style.display = "block";
+ }
+ else {
+ allClassesLink.style.display = "none";
+ }
+ //-->
+</script>
+</div>
+<a name="skip.navbar.bottom">
+<!-- -->
+</a></div>
+<!-- ======== END OF BOTTOM NAVBAR ======= -->
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
+</body>
+</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class GraphicalUI (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class Main (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class MapReclaimer (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class ScreenshotReclaimer (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Class TerminalUI (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
<script type="text/javascript" src="../script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Constant Field Values (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
</tr>
<tbody>
<tr class="altColor">
+<td class="colFirst"><a name="Arbiter.GAME_MODES_DIR">
+<!-- -->
+</a><code>public static final java.lang.String</code></td>
+<td><code><a href="Arbiter.html#GAME_MODES_DIR">GAME_MODES_DIR</a></code></td>
+<td class="colLast"><code>"game-modes"</code></td>
+</tr>
+<tr class="rowColor">
<td class="colFirst"><a name="Arbiter.MAPS_DIR">
<!-- -->
</a><code>public static final java.lang.String</code></td>
<td><code><a href="Arbiter.html#MAPS_DIR">MAPS_DIR</a></code></td>
-<td class="colLast"><code>"maps-reclaimed"</code></td>
+<td class="colLast"><code>"maps"</code></td>
+</tr>
+<tr class="altColor">
+<td class="colFirst"><a name="Arbiter.OUTPUT_DIR">
+<!-- -->
+</a><code>public static final java.lang.String</code></td>
+<td><code><a href="Arbiter.html#OUTPUT_DIR">OUTPUT_DIR</a></code></td>
+<td class="colLast"><code>"halo-3-reclaimed-files"</code></td>
</tr>
<tr class="rowColor">
<td class="colFirst"><a name="Arbiter.SCREENSHOTS_DIR">
<!-- -->
</a><code>public static final java.lang.String</code></td>
<td><code><a href="Arbiter.html#SCREENSHOTS_DIR">SCREENSHOTS_DIR</a></code></td>
-<td class="colLast"><code>"screenshots-reclaimed"</code></td>
+<td class="colLast"><code>"screenshots"</code></td>
</tr>
</tbody>
</table>
<!-- -->
</a><code>public static final java.lang.String</code></td>
<td><code><a href="ErrorHandler.html#BAD_OUTPUT">BAD_OUTPUT</a></code></td>
-<td class="colLast"><code>"123: Output directory problem"</code></td>
+<td class="colLast"><code>"124: Output directory problem"</code></td>
</tr>
<tr class="altColor">
<td class="colFirst"><a name="ErrorHandler.CANT_PROCESS">
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Deprecated List (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>API Help (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Index (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ========= END OF TOP NAVBAR ========= -->
-<div class="contentContainer"><a href="#I:A">A</a> <a href="#I:B">B</a> <a href="#I:C">C</a> <a href="#I:D">D</a> <a href="#I:E">E</a> <a href="#I:F">F</a> <a href="#I:G">G</a> <a href="#I:M">M</a> <a href="#I:N">N</a> <a href="#I:P">P</a> <a href="#I:S">S</a> <a href="#I:T">T</a> <a href="#I:U">U</a> <a name="I:A">
+<div class="contentContainer"><a href="#I:A">A</a> <a href="#I:B">B</a> <a href="#I:C">C</a> <a href="#I:D">D</a> <a href="#I:E">E</a> <a href="#I:F">F</a> <a href="#I:G">G</a> <a href="#I:M">M</a> <a href="#I:N">N</a> <a href="#I:O">O</a> <a href="#I:P">P</a> <a href="#I:S">S</a> <a href="#I:T">T</a> <a href="#I:U">U</a> <a name="I:A">
<!-- -->
</a>
<h2 class="title">A</h2>
<dd>
<div class="block">TUI only: print error AND QUIT</div>
</dd>
+<dt><span class="memberNameLink"><a href="GameModeReclaimer.html#extractGameMode-java.io.File-java.io.File-">extractGameMode(File, File)</a></span> - Method in class <a href="GameModeReclaimer.html" title="class in <Unnamed>">GameModeReclaimer</a></dt>
+<dd>
+<div class="block">Extracts the game mode from an Xbox 360 file and saves it, together with
+ the package it came in, under its in-game name in the output directory.</div>
+</dd>
<dt><span class="memberNameLink"><a href="MapReclaimer.html#extractMap-java.io.File-java.io.File-">extractMap(File, File)</a></span> - Method in class <a href="MapReclaimer.html" title="class in <Unnamed>">MapReclaimer</a></dt>
<dd>
-<div class="block">Extracts the Forge map from an Xbox 360 file and saves it as
- <input name>-reclaimed.map in the output directory.</div>
+<div class="block">Extracts the Forge map from an Xbox 360 file and saves it, together with
+ the package it came in, under the map's in-game name in the output directory.</div>
</dd>
<dt><span class="memberNameLink"><a href="ScreenshotReclaimer.html#extractScreenshot-java.io.File-java.io.File-">extractScreenshot(File, File)</a></span> - Method in class <a href="ScreenshotReclaimer.html" title="class in <Unnamed>">ScreenshotReclaimer</a></dt>
<dd>
</a>
<h2 class="title">G</h2>
<dl>
+<dt><span class="memberNameLink"><a href="Arbiter.html#GAME_MODES_DIR">GAME_MODES_DIR</a></span> - Static variable in class <a href="Arbiter.html" title="class in <Unnamed>">Arbiter</a></dt>
+<dd> </dd>
+<dt><a href="GameModeReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">GameModeReclaimer</span></a> - Class in <a href="package-summary.html"><Unnamed></a></dt>
+<dd>
+<div class="block">Core extraction engine for custom game types (game modes).</div>
+</dd>
+<dt><span class="memberNameLink"><a href="GameModeReclaimer.html#GameModeReclaimer--">GameModeReclaimer()</a></span> - Constructor for class <a href="GameModeReclaimer.html" title="class in <Unnamed>">GameModeReclaimer</a></dt>
+<dd> </dd>
<dt><a href="GraphicalUI.html" title="class in <Unnamed>"><span class="typeNameLink">GraphicalUI</span></a> - Class in <a href="package-summary.html"><Unnamed></a></dt>
<dd>
<div class="block">Graphical User Interface for Halo 3 Reclaimer.</div>
<dt><span class="memberNameLink"><a href="ErrorHandler.html#NOT_READY">NOT_READY</a></span> - Static variable in class <a href="ErrorHandler.html" title="class in <Unnamed>">ErrorHandler</a></dt>
<dd> </dd>
</dl>
+<a name="I:O">
+<!-- -->
+</a>
+<h2 class="title">O</h2>
+<dl>
+<dt><span class="memberNameLink"><a href="Arbiter.html#OUTPUT_DIR">OUTPUT_DIR</a></span> - Static variable in class <a href="Arbiter.html" title="class in <Unnamed>">Arbiter</a></dt>
+<dd> </dd>
+</dl>
<a name="I:P">
<!-- -->
</a>
<dl>
<dt><span class="memberNameLink"><a href="Arbiter.html#processFile-java.io.File-java.io.File-">processFile(File, File)</a></span> - Static method in class <a href="Arbiter.html" title="class in <Unnamed>">Arbiter</a></dt>
<dd>
-<div class="block">Reclaims all screenshots and maps found in a file or folder.</div>
+<div class="block">Reclaims all screenshots, maps and game modes found in a file or folder.</div>
</dd>
<dt><span class="memberNameLink"><a href="TerminalUI.html#processFile-java.lang.String:A-">processFile(String[])</a></span> - Static method in class <a href="TerminalUI.html" title="class in <Unnamed>">TerminalUI</a></dt>
<dd>
<dt><span class="memberNameLink"><a href="ErrorHandler.html#UNKNOWN_FILE">UNKNOWN_FILE</a></span> - Static variable in class <a href="ErrorHandler.html" title="class in <Unnamed>">ErrorHandler</a></dt>
<dd> </dd>
</dl>
-<a href="#I:A">A</a> <a href="#I:B">B</a> <a href="#I:C">C</a> <a href="#I:D">D</a> <a href="#I:E">E</a> <a href="#I:F">F</a> <a href="#I:G">G</a> <a href="#I:M">M</a> <a href="#I:N">N</a> <a href="#I:P">P</a> <a href="#I:S">S</a> <a href="#I:T">T</a> <a href="#I:U">U</a> </div>
+<a href="#I:A">A</a> <a href="#I:B">B</a> <a href="#I:C">C</a> <a href="#I:D">D</a> <a href="#I:E">E</a> <a href="#I:F">F</a> <a href="#I:G">G</a> <a href="#I:M">M</a> <a href="#I:N">N</a> <a href="#I:O">O</a> <a href="#I:P">P</a> <a href="#I:S">S</a> <a href="#I:T">T</a> <a href="#I:U">U</a> </div>
<!-- ======= START OF BOTTOM NAVBAR ====== -->
<div class="bottomNav"><a name="navbar.bottom">
<!-- -->
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Halo 3 Reclaimer</title>
<script type="text/javascript">
tmpTargetPage = "" + window.location.search;
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Overview (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
</a></div>
<!-- ========= END OF TOP NAVBAR ========= -->
<div class="header">
-<h1 class="title">Halo 3 Reclaimer - File extractor from Xbox 360 version of Halo 3</h1>
+<h1 class="title">Halo 3 Reclaimer - player data extractor from Xbox 360 version of Halo 3</h1>
</div>
<div class="contentContainer">
<table class="overviewSummary" border="0" cellpadding="3" cellspacing="0" summary="Packages table, listing packages, and an explanation">
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Class Hierarchy (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
</li>
<li type="circle"><a href="Cortana.html" title="class in <Unnamed>"><span class="typeNameLink">Cortana</span></a></li>
<li type="circle"><a href="ErrorHandler.html" title="class in <Unnamed>"><span class="typeNameLink">ErrorHandler</span></a></li>
+<li type="circle"><a href="GameModeReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">GameModeReclaimer</span></a></li>
<li type="circle"><a href="Main.html" title="class in <Unnamed>"><span class="typeNameLink">Main</span></a></li>
<li type="circle"><a href="MapReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">MapReclaimer</span></a></li>
<li type="circle"><a href="ScreenshotReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">ScreenshotReclaimer</span></a></li>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title><Unnamed> (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<li><a href="Arbiter.html" title="class in <Unnamed>" target="classFrame">Arbiter</a></li>
<li><a href="Cortana.html" title="class in <Unnamed>" target="classFrame">Cortana</a></li>
<li><a href="ErrorHandler.html" title="class in <Unnamed>" target="classFrame">ErrorHandler</a></li>
+<li><a href="GameModeReclaimer.html" title="class in <Unnamed>" target="classFrame">GameModeReclaimer</a></li>
<li><a href="GraphicalUI.html" title="class in <Unnamed>" target="classFrame">GraphicalUI</a></li>
<li><a href="Main.html" title="class in <Unnamed>" target="classFrame">Main</a></li>
<li><a href="MapReclaimer.html" title="class in <Unnamed>" target="classFrame">MapReclaimer</a></li>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title> (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
</td>
</tr>
<tr class="rowColor">
+<td class="colFirst"><a href="GameModeReclaimer.html" title="class in <Unnamed>">GameModeReclaimer</a></td>
+<td class="colLast">
+<div class="block">Core extraction engine for custom game types (game modes).</div>
+</td>
+</tr>
+<tr class="altColor">
<td class="colFirst"><a href="GraphicalUI.html" title="class in <Unnamed>">GraphicalUI</a></td>
<td class="colLast">
<div class="block">Graphical User Interface for Halo 3 Reclaimer.</div>
</td>
</tr>
-<tr class="altColor">
+<tr class="rowColor">
<td class="colFirst"><a href="Main.html" title="class in <Unnamed>">Main</a></td>
<td class="colLast">
<div class="block">Main entry point for the program.</div>
</td>
</tr>
-<tr class="rowColor">
+<tr class="altColor">
<td class="colFirst"><a href="MapReclaimer.html" title="class in <Unnamed>">MapReclaimer</a></td>
<td class="colLast">
<div class="block">Core extraction engine for Forge map variants.</div>
</td>
</tr>
-<tr class="altColor">
+<tr class="rowColor">
<td class="colFirst"><a href="ScreenshotReclaimer.html" title="class in <Unnamed>">ScreenshotReclaimer</a></td>
<td class="colLast">
<div class="block">Core extraction engine for screenshots.</div>
</td>
</tr>
-<tr class="rowColor">
+<tr class="altColor">
<td class="colFirst"><a href="TerminalUI.html" title="class in <Unnamed>">TerminalUI</a></td>
<td class="colLast">
<div class="block">Terminal User Interface for Halo 3 Reclaimer.</div>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title> Class Hierarchy (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
</li>
<li type="circle"><a href="Cortana.html" title="class in <Unnamed>"><span class="typeNameLink">Cortana</span></a></li>
<li type="circle"><a href="ErrorHandler.html" title="class in <Unnamed>"><span class="typeNameLink">ErrorHandler</span></a></li>
+<li type="circle"><a href="GameModeReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">GameModeReclaimer</span></a></li>
<li type="circle"><a href="Main.html" title="class in <Unnamed>"><span class="typeNameLink">Main</span></a></li>
<li type="circle"><a href="MapReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">MapReclaimer</span></a></li>
<li type="circle"><a href="ScreenshotReclaimer.html" title="class in <Unnamed>"><span class="typeNameLink">ScreenshotReclaimer</span></a></li>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Uses of Package (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
<!-- NewPage -->
<html lang="en">
<head>
-<!-- Generated by javadoc (1.8.0_504) on Mon Nov 02 17:45:23 CET 2026 -->
+<!-- Generated by javadoc (1.8.0_504) on Tue Nov 03 11:00:11 CET 2026 -->
<title>Serialized Form (Halo 3 Reclaimer)</title>
-<meta name="date" content="2026-11-02">
+<meta name="date" content="2026-11-03">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<!-- -->
</a></div>
<!-- ======== END OF BOTTOM NAVBAR ======= -->
-<p class="legalCopy"><small>Copyright 2025. Documentation licensed under GFDL v1.3.</small></p>
+<p class="legalCopy"><small>Copyright 2025, 2026. Documentation licensed under GFDL v1.3.</small></p>
</body>
</html>
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
/**
* Arbiter class that determines file type and routes to appropriate extractor.
* Cortana first, which copies the Halo 3 files out of the Xbox file system;
* other files on the stick are ignored then.</p>
*
-* <p>Halo 3 keeps every screenshot and every Forge map in its own package,
-* so the Arbiter looks inside each file: a package holding
+* <p>Halo 3 keeps every screenshot, Forge map and custom game type in its own
+* package, so the Arbiter looks inside each file: a package holding
* <code>screen.shot</code> is a screenshot, one holding <code>sandbox.map</code>
-* is a map. Screenshots are checked first, then maps. Anything else is skipped.
-* Only when the content says nothing (DDS, TGA, raw textures) the file name
-* decides, like in version 1.0.</p>
+* is a map and one holding <code>variant.slayer</code>, <code>variant.ctf</code>...
+* is a game mode. Screenshots are checked first, then maps, then game modes.
+* Anything else is skipped. Only when the content says nothing (DDS, TGA,
+* raw textures) the file name decides, like in version 1.0.</p>
*
-* <p>Results go to two folders in the output directory:
-* <code>screenshots-reclaimed</code> and <code>maps-reclaimed</code>.</p>
+* <p>Results go to <code>halo-3-reclaimed-files</code> in the output directory,
+* sorted into <code>screenshots</code>, <code>maps</code> and <code>game-modes</code>.
+* Files are named after their in-game names, see {@link #saveFiles}.</p>
*
-* @version 1.1
+* @version 1.2
*/
public class Arbiter {
- // folders created inside the output directory
- public static final String SCREENSHOTS_DIR = "screenshots-reclaimed";
- public static final String MAPS_DIR = "maps-reclaimed";
+ // everything goes into one folder inside the output directory, sorted by kind
+ public static final String OUTPUT_DIR = "halo-3-reclaimed-files";
+ public static final String SCREENSHOTS_DIR = "screenshots";
+ public static final String MAPS_DIR = "maps";
+ public static final String GAME_MODES_DIR = "game-modes";
+
+ // output folders of older test builds, never read back either
+ private static final String[] OLD_OUTPUT_DIRS = { "screenshots-reclaimed", "maps-reclaimed" };
// the file inside a package that holds a screenshot
private static final String SCREENSHOT_FILE = "screen.shot";
// how deep to look into sub folders
private static final int MAX_FOLDER_DEPTH = 8;
- private enum Kind { SCREENSHOT, MAP, OTHER }
+ private enum Kind { SCREENSHOT, MAP, GAME_MODE, OTHER }
/**
- * Reclaims all screenshots and maps found in a file or folder.
+ * Reclaims all screenshots, maps and game modes found in a file or folder.
*
- * @return what was reclaimed, for example "Reclaimed 2 pictures and 1 map"
+ * @return what was reclaimed, for example "Reclaimed 2 pictures, 1 map and 3 game modes"
* @throws Exception if nothing could be reclaimed
*/
public static String processFile(File input, File outputDir) throws Exception {
}
/**
- * Sorts the files out and runs the right reclaimer on each, screenshots first.
+ * Sorts the files out and runs the right reclaimer on each: screenshots first,
+ * then maps, then game modes.
* folder: there are many files to go through, root: where console paths start.
*/
private static String reclaim(List<File> files, File outputDir, boolean folder, File root) throws Exception {
- // sort everything out first, screenshots go first
+ // sort everything out first
List<File> screenshots = new ArrayList<File>();
List<File> maps = new ArrayList<File>();
+ List<File> gameModes = new ArrayList<File>();
String skipReason = null;
for (File file : files) {
StringBuilder reason = new StringBuilder();
screenshots.add(file);
} else if (kind == Kind.MAP) {
maps.add(file);
+ } else if (kind == Kind.GAME_MODE) {
+ gameModes.add(file);
} else {
// short reason for the status bar, the detail only goes to the console
skipReason = reason.toString();
}
}
- if (screenshots.isEmpty() && maps.isEmpty()) {
+ if (screenshots.isEmpty() && maps.isEmpty() && gameModes.isEmpty()) {
if (folder) {
throw ErrorHandler.makeException(ErrorHandler.UNKNOWN_FILE + ": no Halo 3 files found");
}
throw ErrorHandler.makeException(skipReason);
}
if (folder) {
- System.out.println("Found " + count(screenshots.size(), "screenshot")
- + " and " + count(maps.size(), "map"));
+ System.out.println("Found " + list(count(screenshots.size(), "screenshot"),
+ count(maps.size(), "map"), count(gameModes.size(), "game mode")));
}
+ File reclaimedDir = new File(outputDir, OUTPUT_DIR);
int failed = 0;
Exception lastError = null;
int pictures = 0;
if (!screenshots.isEmpty()) {
- File screenshotsDir = outputFolder(outputDir, SCREENSHOTS_DIR);
+ File screenshotsDir = outputFolder(reclaimedDir, SCREENSHOTS_DIR);
for (File file : screenshots) {
System.out.println("Detected as screenshot: " + shown(root, file));
try {
int reclaimedMaps = 0;
if (!maps.isEmpty()) {
- File mapsDir = outputFolder(outputDir, MAPS_DIR);
+ File mapsDir = outputFolder(reclaimedDir, MAPS_DIR);
for (File file : maps) {
System.out.println("Detected as Forge map: " + shown(root, file));
try {
}
}
- if (pictures + reclaimedMaps == 0) {
+ int reclaimedGameModes = 0;
+ if (!gameModes.isEmpty()) {
+ File gameModesDir = outputFolder(reclaimedDir, GAME_MODES_DIR);
+ for (File file : gameModes) {
+ System.out.println("Detected as game mode: " + shown(root, file));
+ try {
+ new GameModeReclaimer().extractGameMode(file, gameModesDir);
+ reclaimedGameModes++;
+ } catch (Exception e) {
+ failed++;
+ lastError = e;
+ System.out.println("Could not reclaim " + shown(root, file) + ": " + e.getMessage());
+ }
+ }
+ }
+
+ if (pictures + reclaimedMaps + reclaimedGameModes == 0) {
if (failed == 1) {
throw lastError;
}
throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": all " + failed + " files failed");
}
- List<String> reclaimed = new ArrayList<String>();
- if (pictures > 0) reclaimed.add(count(pictures, "picture"));
- if (reclaimedMaps > 0) reclaimed.add(count(reclaimedMaps, "map"));
- String result = "Reclaimed " + String.join(" and ", reclaimed);
+ String result = "Reclaimed " + list(
+ pictures > 0 ? count(pictures, "picture") : null,
+ reclaimedMaps > 0 ? count(reclaimedMaps, "map") : null,
+ reclaimedGameModes > 0 ? count(reclaimedGameModes, "game mode") : null);
if (failed > 0) {
result += " (" + failed + " failed)";
}
}
/**
- * Tells what a file is by looking inside it. Screenshots are checked first.
- * When the file is neither, a short error goes into reason and, when
- * there is more to say, an explanation for the console into detail.
+ * Tells what a file is by looking inside it. Screenshots are checked first,
+ * then maps, then game modes. When the file is none of them, a short error
+ * goes into reason and, when there is more to say, an explanation for the
+ * console into detail.
*/
private static Kind detect(File file, StringBuilder reason, StringBuilder detail) {
if (file.length() > MAX_FILE_SIZE) {
reason.append(ErrorHandler.CANT_PROCESS + ": file too big");
- detail.append(file.length() + " bytes, too big for a screenshot or map");
+ detail.append(file.length() + " bytes, too big for a Halo 3 save");
return Kind.OTHER;
}
List<String> names = MapReclaimer.listPackageFiles(data);
if (containsName(names, SCREENSHOT_FILE)) return Kind.SCREENSHOT;
if (containsName(names, MapReclaimer.MAP_VARIANT_FILE)) return Kind.MAP;
- // game types, films...
- reason.append(ErrorHandler.CANT_PROCESS + ": not a screenshot or map");
+ if (GameModeReclaimer.findGameModeFile(names) != null) return Kind.GAME_MODE;
+ // films, campaign saves...
+ reason.append(ErrorHandler.CANT_PROCESS + ": not a screenshot, map or game mode");
detail.append(names.isEmpty() ? "the package is empty"
: "the package holds " + String.join(", ", names));
return Kind.OTHER;
reason.append(e.getMessage() != null ? e.getMessage() : ErrorHandler.CANT_PROCESS);
}
} else if (MapReclaimer.isBLFFile(data)) {
- // screen.shot or sandbox.map already taken out of its package
+ // screen.shot, sandbox.map or variant.* already taken out of its package
int fileType = MapReclaimer.readBlfFileType(data);
if (fileType == MapReclaimer.FILE_TYPE_SCREENSHOT) return Kind.SCREENSHOT;
if (fileType == MapReclaimer.FILE_TYPE_MAP_VARIANT) return Kind.MAP;
+ if (GameModeReclaimer.isGameModeType(fileType)) return Kind.GAME_MODE;
if (fileType >= 0) {
- reason.append(ErrorHandler.CANT_PROCESS + ": not a screenshot or map");
- detail.append("Halo file of type " + fileType + " (game type, film...)");
+ reason.append(ErrorHandler.CANT_PROCESS + ": not a screenshot, map or game mode");
+ detail.append("Halo file of type " + fileType + " (film, campaign save...)");
return Kind.OTHER;
}
}
return false;
}
+ /**
+ * Saves reclaimed files under their in-game name: all lowercase, spaces as
+ * underscores, e.g. <code>guardian_infection.map</code> and
+ * <code>guardian_infection.package</code>. When a different file already uses
+ * that name, a number is added (<code>guardian_infection_2.map</code>); the very
+ * same file reclaimed again just keeps its name, so running the reclaimer twice
+ * doesn't make copies.
+ *
+ * @param inGameName name stored in the file, can be empty
+ * @param fallback used when the in-game name is empty, e.g. the package name
+ * @param files extension (with the dot) and contents of each file to save
+ * @return the name used, without extension
+ * @throws Exception if a file can't be written
+ */
+ static String saveFiles(File folder, String inGameName, String fallback, Map<String, byte[]> files) throws Exception {
+ String base = toFileName(inGameName);
+ if (base.isEmpty()) base = toFileName(fallback);
+ if (base.isEmpty()) base = "unnamed";
+
+ for (int number = 1; number < 10000; number++) {
+ String name = (number == 1) ? base : base + "_" + number;
+ if (isFree(folder, name, files)) {
+ for (Map.Entry<String, byte[]> file : files.entrySet()) {
+ File target = new File(folder, name + file.getKey());
+ writeFile(target, file.getValue());
+ System.out.println("Saved as: " + target.getPath());
+ }
+ return name;
+ }
+ }
+ System.out.println("Too many files named " + base + " in " + folder.getPath());
+ throw ErrorHandler.makeException(ErrorHandler.BAD_OUTPUT + ": too many same names");
+ }
+
+ /**
+ * In-game name to file name: lowercase, spaces become underscores, and so does
+ * everything Windows doesn't allow in file names.
+ */
+ static String toFileName(String name) {
+ if (name == null) return "";
+ String lower = name.trim().toLowerCase(Locale.ROOT);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < lower.length(); i++) {
+ char c = lower.charAt(i);
+ boolean forbidden = c == ' ' || Character.isISOControl(c) || "\\/:*?\"<>|".indexOf(c) >= 0;
+ sb.append(forbidden ? '_' : c);
+ }
+ String fileName = sb.toString();
+ // Windows also refuses names ending in a dot and a few device names like "con"
+ if (fileName.endsWith(".")) fileName += "_";
+ if (fileName.matches("con|prn|aux|nul|com[1-9]|lpt[1-9]")) fileName += "_";
+ return fileName;
+ }
+
+ // true when each of these files is new, or already there with exactly the same contents
+ private static boolean isFree(File folder, String name, Map<String, byte[]> files) {
+ for (Map.Entry<String, byte[]> file : files.entrySet()) {
+ File existing = new File(folder, name + file.getKey());
+ if (!existing.exists()) continue;
+ try {
+ if (!existing.isFile() || existing.length() != file.getValue().length
+ || !Arrays.equals(readFile(existing), file.getValue())) {
+ return false;
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static void writeFile(File file, byte[] data) throws Exception {
+ try (FileOutputStream out = new FileOutputStream(file)) {
+ out.write(data);
+ } catch (IOException e) {
+ throw ErrorHandler.makeException(ErrorHandler.BAD_OUTPUT + ": " + e.getMessage());
+ }
+ }
+
private static List<File> listFiles(File folder) {
List<File> files = new ArrayList<File>();
collectFiles(folder, files, 0);
Arrays.sort(children);
for (File child : children) {
if (child.isDirectory()) {
- String name = child.getName();
- if (!name.equals(SCREENSHOTS_DIR) && !name.equals(MAPS_DIR)) {
+ if (!isOutputFolder(child.getName())) {
collectFiles(child, files, depth + 1);
}
} else if (child.isFile()) {
}
}
+ private static boolean isOutputFolder(String name) {
+ if (name.equals(OUTPUT_DIR)) return true;
+ for (String old : OLD_OUTPUT_DIRS) {
+ if (name.equals(old)) return true;
+ }
+ return false;
+ }
+
/**
* Returns the output sub folder, creates it when needed.
*/
private static String count(int number, String word) {
return number + " " + word + (number == 1 ? "" : "s");
}
+
+ // "a", "a and b", "a, b and c"; empty parts are left out
+ private static String list(String... parts) {
+ List<String> used = new ArrayList<String>();
+ for (String part : parts) {
+ if (part != null) used.add(part);
+ }
+ if (used.size() <= 1) return used.isEmpty() ? "" : used.get(0);
+ return String.join(", ", used.subList(0, used.size() - 1)) + " and " + used.get(used.size() - 1);
+ }
}
*
* <p>This class just stores error messages and prints them when needed.</p>
*
-* @version 1.0
+* @version 1.1
*/
public class ErrorHandler {
// Error messages
public static final String UNKNOWN_FILE = "343: Not a Halo game (file)";
public static final String TOO_MANY_ARGS = "002: Too many arguments";
- public static final String BAD_INPUT = "123: Input file problem";
- public static final String BAD_OUTPUT = "123: Output directory problem";
+ public static final String BAD_INPUT = "011: Input file problem";
+ public static final String BAD_OUTPUT = "012: Output directory problem";
public static final String CANT_PROCESS = "003: Cannot process file";
public static final String NOT_READY = "117: Feature not ready yet";
public static final String NEED_SELECTION = "001: Please select file and directory";
public static Exception makeException(String errorMessage) {
return new Exception(errorMessage);
}
-}
\ No newline at end of file
+}
--- /dev/null
+/*
+* Halo 3 Reclaimer [GameModeReclaimer.java]
+* player data extractor from Xbox 360 version of Halo 3
+* copyright 2026, David Polakovic, licensed under GPLv3
+* contact: email@dpolakovic.space
+*/
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+* Core extraction engine for custom game types (game modes).
+*
+* <p>Halo 3 saves every custom game type as its own Xbox 360 package, just
+* like the Forge maps. The package holds one file named after the game's
+* rules, for example <code>variant.slayer</code> or <code>variant.ctf</code>.
+* That file is the game mode itself, all of its settings, stored in Bungie's
+* BLF format. The package and BLF readers are shared with MapReclaimer.</p>
+*
+* <p>Supported input formats:</p>
+* <ul>
+* <li>STFS container files holding a Halo 3 game type</li>
+* <li>already extracted <code>variant.*</code> files (BLF)</li>
+* </ul>
+*
+* <p>Two files are saved, named after the game mode (see Arbiter.saveFiles):</p>
+* <ul>
+* <li><code>team_snipers.slayer</code>: byte for byte the file the console
+* keeps inside the package, its ending tells the game type</li>
+* <li><code>team_snipers.package</code>: the untouched package, which tools
+* like Horizon or Velocity can sign for another player's profile
+* (only when the input was a package)</li>
+* </ul>
+*
+* @version 1.0
+*/
+
+public class GameModeReclaimer {
+
+ // the file inside the package is called variant.<game type>
+ private static final String GAME_MODE_FILE_PREFIX = "variant";
+
+ // saved game file types 1 to 9 are the game modes, in this order
+ private static final int FIRST_GAME_MODE_TYPE = 1;
+ private static final int LAST_GAME_MODE_TYPE = 9;
+ private static final String[] GAME_MODE_NAMES = {
+ null, "Capture the Flag", "Slayer", "Oddball", "King of the Hill",
+ "Juggernaut", "Territories", "Assault", "Infection", "VIP"
+ };
+
+ // file endings for bare files whose name doesn't tell (packages always do)
+ private static final String[] GAME_MODE_EXTENSIONS = {
+ null, "ctf", "slayer", "oddball", "king", "juggernaut",
+ "territories", "assault", "infection", "vip"
+ };
+
+ /**
+ * Extracts the game mode from an Xbox 360 file and saves it, together with
+ * the package it came in, under its in-game name in the output directory.
+ *
+ * @throws Exception if the file cannot be read, is not a Halo 3 game mode,
+ * or is damaged
+ */
+ public void extractGameMode(File inputFile, File outputDir) throws Exception {
+ byte[] fileData = MapReclaimer.readInputFile(inputFile);
+
+ System.out.println("File size: " + fileData.length + " bytes");
+
+ byte[] variantData;
+ byte[] packageData = null;
+ String extension;
+ MapReclaimer.ContentInfo info;
+ try {
+ if (MapReclaimer.isSTFSFile(fileData)) {
+ System.out.println("Detected STFS file format");
+ String variantFile = findGameModeFile(MapReclaimer.listPackageFiles(fileData));
+ variantData = (variantFile == null) ? null : MapReclaimer.readPackageFile(fileData, variantFile);
+ if (variantData == null) {
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": no game mode inside");
+ }
+ extension = extensionOf(variantFile);
+ packageData = fileData;
+ } else if (MapReclaimer.isBLFFile(fileData)) {
+ System.out.println("Detected extracted game mode (BLF) file");
+ variantData = fileData;
+ extension = extensionOf(inputFile.getName());
+ } else {
+ System.out.println("Not an Xbox 360 package or game mode");
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": not a game mode");
+ }
+ info = readGameModeInfo(variantData);
+ } catch (RuntimeException e) {
+ // a bug or a file layout nobody has seen yet, keep the GUI informed
+ e.printStackTrace();
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": damaged or unsupported file");
+ }
+
+ if (extension == null) {
+ extension = GAME_MODE_EXTENSIONS[info.fileType];
+ }
+
+ System.out.println("Game mode: \"" + info.name + "\" (" + GAME_MODE_NAMES[info.fileType] + ")");
+ if (!info.description.isEmpty()) {
+ System.out.println("Description: " + info.description);
+ }
+ if (!info.author.isEmpty()) {
+ System.out.println("Author: " + info.author);
+ }
+ System.out.println("Game mode size: " + variantData.length + " bytes");
+
+ // the game mode itself, and the package it came in, ready to be signed for another player
+ Map<String, byte[]> files = new LinkedHashMap<String, byte[]>();
+ files.put("." + extension, variantData);
+ if (packageData != null) {
+ files.put(MapReclaimer.PACKAGE_EXTENSION, packageData);
+ } else {
+ System.out.println("No package to save, the input was a bare game mode file");
+ }
+ Arbiter.saveFiles(outputDir, info.name, MapReclaimer.baseName(inputFile), files);
+ }
+
+ /**
+ * The game mode file among the files of a package (variant.slayer,
+ * variant.ctf...), or null if there is none.
+ */
+ static String findGameModeFile(List<String> names) {
+ for (String name : names) {
+ if (name.toLowerCase(Locale.ROOT).startsWith(GAME_MODE_FILE_PREFIX)) return name;
+ }
+ return null;
+ }
+
+ /**
+ * True for the saved game file types of game modes (1 to 9).
+ */
+ static boolean isGameModeType(int fileType) {
+ return fileType >= FIRST_GAME_MODE_TYPE && fileType <= LAST_GAME_MODE_TYPE;
+ }
+
+ /**
+ * Makes sure the BLF file really is a complete game mode and reads its metadata.
+ */
+ private MapReclaimer.ContentInfo readGameModeInfo(byte[] blf) throws Exception {
+ if (!MapReclaimer.isBLFFile(blf)) {
+ System.out.println("The game mode file is not a BLF file");
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": not a game mode");
+ }
+
+ MapReclaimer.ContentInfo info = MapReclaimer.readContentInfo(blf);
+ if (info == null) {
+ System.out.println("Game mode has a broken chunk");
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": damaged game mode");
+ }
+ if (!info.complete) {
+ System.out.println("Game mode is incomplete, its end of file chunk is missing");
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": damaged game mode");
+ }
+ if (!isGameModeType(info.fileType)) {
+ System.out.println("This Halo file is of type " + info.fileType + ", not a game mode");
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": not a game mode");
+ }
+ return info;
+ }
+
+ // "variant.slayer" -> "slayer", null when the name doesn't tell
+ private static String extensionOf(String fileName) {
+ String lower = fileName.toLowerCase(Locale.ROOT);
+ if (!lower.startsWith(GAME_MODE_FILE_PREFIX + ".")) return null;
+ String extension = Arbiter.toFileName(lower.substring(GAME_MODE_FILE_PREFIX.length() + 1));
+ return extension.isEmpty() ? null : extension;
+ }
+}
* <p>This class contains all the GUI components and
* logic for the application's windowed interface</p>
*
-* @version 1.1
+* @version 1.2
*/
public class GraphicalUI extends JFrame {
*/
private void showVersionInfo() {
String versionText = "Halo 3 Reclaimer\n\n" +
- "Version: 0.2\n\n" +
+ "Version: 0.3\n\n" +
"player data extractor from Xbox 360 version of Halo 3\n" +
"copyright 2025, 2026, David Polakovic, licensed under GPLv3\n";
"The tool will extract your user data from the\n" +
"Xbox 360 save. Currently supports:\n" +
"-screenshots\n" +
- "-Forge maps\n\n";
+ "-Forge map variants\n" +
+ "-Custom game types\n\n";
JOptionPane.showMessageDialog(this, helpText, "Help",
JOptionPane.INFORMATION_MESSAGE);
GraphicalUI.main();
}
}
-}
\ No newline at end of file
+}
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* Core extraction engine for Forge map variants.
* <p>Unlike screenshots, the map has no magic bytes worth scanning for,
* so this class reads the STFS file system properly: it walks the file
* table, follows the block chain of <code>sandbox.map</code> and checks
-* each block against the package hash tables.</p>
+* each block against the package hash tables. These package and BLF readers
+* are shared with the other reclaimers.</p>
*
* <p>Supported input formats:</p>
* <ul>
* <li>already extracted <code>sandbox.map</code> files (BLF)</li>
* </ul>
*
-* <p>The saved file is not converted in any way. It is byte for byte the
-* <code>sandbox.map</code> the console keeps inside the package.</p>
+* <p>Two files are saved, named after the map (see Arbiter.saveFiles):</p>
+* <ul>
+* <li><code>guardian_infection.map</code>: byte for byte the
+* <code>sandbox.map</code> the console keeps inside the package</li>
+* <li><code>guardian_infection.package</code>: the untouched package, which
+* tools like Horizon or Velocity can sign for another player's profile
+* (only when the input was a package)</li>
+* </ul>
*
-* @version 1.0
+* @version 1.1
*/
public class MapReclaimer {
// the file inside the package that holds the Forge map
static final String MAP_VARIANT_FILE = "sandbox.map";
+ // packages are saved untouched under this extension
+ static final String PACKAGE_EXTENSION = ".package";
+
// BLF format constants (the format of sandbox.map)
private static final int BLF_START = 0x5F626C66; // "_blf"
private static final int BLF_END = 0x5F656F66; // "_eof"
};
/**
- * Extracts the Forge map from an Xbox 360 file and saves it as
- * <input name>-reclaimed.map in the output directory.
+ * Extracts the Forge map from an Xbox 360 file and saves it, together with
+ * the package it came in, under the map's in-game name in the output directory.
*
* @throws Exception if the file cannot be read, is not a Halo 3 Forge map,
* or is damaged
*/
public void extractMap(File inputFile, File outputDir) throws Exception {
- byte[] fileData = readFile(inputFile);
+ byte[] fileData = readInputFile(inputFile);
System.out.println("File size: " + fileData.length + " bytes");
byte[] mapData;
- MapInfo info;
+ byte[] packageData = null;
+ ContentInfo info;
try {
if (isSTFSFile(fileData)) {
System.out.println("Detected STFS file format");
- mapData = extractFromSTFS(fileData);
+ mapData = readPackageFile(fileData, MAP_VARIANT_FILE);
+ if (mapData == null) {
+ // the "Package contains" lines tell what is in there instead
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": no Forge map inside");
+ }
+ packageData = fileData;
} else if (isBLFFile(fileData)) {
System.out.println("Detected extracted map variant (BLF) file");
mapData = fileData;
}
System.out.println("Map variant size: " + mapData.length + " bytes");
- // generate output filename, same scheme as the screenshots
- String baseName = inputFile.getName();
- int dotIndex = baseName.lastIndexOf('.');
- if (dotIndex > 0) {
- baseName = baseName.substring(0, dotIndex);
+ // the map itself, and the package it came in, ready to be signed for another player
+ Map<String, byte[]> files = new LinkedHashMap<String, byte[]>();
+ files.put(".map", mapData);
+ if (packageData != null) {
+ files.put(PACKAGE_EXTENSION, packageData);
+ } else {
+ System.out.println("No package to save, the input was a bare map variant");
}
- String outputFileName = baseName + "-reclaimed.map";
- File outputFile = new File(outputDir, outputFileName);
-
- writeFile(outputFile, mapData);
- System.out.println("Saved as: " + outputFile.getAbsolutePath());
+ Arbiter.saveFiles(outputDir, info.name, baseName(inputFile), files);
}
/**
- * Reads the entire contents of a file into a byte array.
+ * Reads the entire contents of an input file into a byte array.
+ * Halo 3 packages are small, anything huge is refused.
*/
- private byte[] readFile(File file) throws Exception {
+ static byte[] readInputFile(File file) throws Exception {
long length = file.length();
if (length > MAX_INPUT_SIZE) {
- throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": file too big for a Forge map");
+ throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": file too big");
}
byte[] data = new byte[(int) length];
return data;
}
- private void writeFile(File file, byte[] data) throws Exception {
- try (FileOutputStream out = new FileOutputStream(file)) {
- out.write(data);
- } catch (IOException e) {
- throw ErrorHandler.makeException(ErrorHandler.BAD_OUTPUT + ": " + e.getMessage());
- }
+ /**
+ * The file name without its extension, the fallback when there is no in-game name.
+ */
+ static String baseName(File file) {
+ String name = file.getName();
+ int dotIndex = name.lastIndexOf('.');
+ return (dotIndex > 0) ? name.substring(0, dotIndex) : name;
}
static boolean isSTFSFile(byte[] data) {
/**
* Lists the files inside a Halo 3 package. The Arbiter uses this to tell
- * screenshots (screen.shot) from maps (sandbox.map) without trusting file names.
+ * screenshots, maps and game modes apart without trusting file names.
*
* @throws Exception if the package is damaged or belongs to another game
*/
}
/**
- * Saved game file type of an already extracted BLF file
- * (FILE_TYPE_SCREENSHOT, FILE_TYPE_MAP_VARIANT...), or -1 if it can't be told.
- */
- static int readBlfFileType(byte[] blf) {
- if (!isBLFFile(blf)) return -1;
- int[] chunks = findBlfChunks(blf);
- if (chunks == null) return -1;
- if (hasMetadata(blf, chunks[0])) {
- return readInt32(blf, chunks[0] + METADATA_OFFSET + METADATA_FILE_TYPE);
- }
- return chunks[1] >= 0 ? FILE_TYPE_MAP_VARIANT : -1;
- }
-
- /**
- * Finds sandbox.map in the package and returns its contents.
+ * Reads one file out of a Halo 3 package and prints what the package holds.
+ *
+ * @return the file contents, or null when the package has no file of that name
+ * @throws Exception if the package is damaged or belongs to another game
*/
- private byte[] extractFromSTFS(byte[] data) throws Exception {
+ static byte[] readPackageFile(byte[] data, String fileName) throws Exception {
StfsPackage stfs = new StfsPackage(data);
System.out.printf("Package: \"%s\", title ID %08X%n", stfs.displayName, stfs.titleId);
throw ErrorHandler.makeException(ErrorHandler.UNKNOWN_FILE + ": not a Halo 3 package");
}
- StfsEntry mapEntry = null;
+ StfsEntry wanted = null;
for (StfsEntry entry : stfs.entries) {
System.out.println("Package contains: " + entry.name
+ (entry.isDirectory ? " (folder)" : " (" + entry.size + " bytes)"));
- if (mapEntry == null && !entry.isDirectory && entry.name.equalsIgnoreCase(MAP_VARIANT_FILE)) {
- mapEntry = entry;
+ if (wanted == null && !entry.isDirectory && entry.name.equalsIgnoreCase(fileName)) {
+ wanted = entry;
}
}
+ if (wanted == null) return null;
- if (mapEntry == null) {
- // the "Package contains" lines above tell what is in there instead
- throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": no Forge map inside");
- }
-
- byte[] mapData = stfs.readFile(mapEntry);
+ byte[] content = stfs.readFile(wanted);
if (stfs.badBlocks > 0) {
- System.out.println("Warning: " + stfs.badBlocks + " block(s) of " + MAP_VARIANT_FILE
+ System.out.println("Warning: " + stfs.badBlocks + " block(s) of " + wanted.name
+ " do not match the package hashes, the package may be damaged");
} else {
System.out.println("Package hashes OK");
}
- return mapData;
+ return content;
+ }
+
+ /**
+ * Saved game file type of an already extracted BLF file
+ * (FILE_TYPE_SCREENSHOT, FILE_TYPE_MAP_VARIANT...), or -1 if it can't be told.
+ */
+ static int readBlfFileType(byte[] blf) {
+ ContentInfo info = readContentInfo(blf);
+ if (info == null) return -1;
+ if (info.fileType >= 0) return info.fileType;
+ return info.hasMapVariant ? FILE_TYPE_MAP_VARIANT : -1;
+ }
+
+ /**
+ * What a saved Halo 3 file (map, game mode, screenshot) says about itself.
+ */
+ static final class ContentInfo {
+ String name = "";
+ String description = "";
+ String author = "";
+ int fileType = -1;
+ int mapId = -1;
+ boolean complete; // it ends with its _eof chunk
+ boolean hasMapVariant; // it has an mvar chunk
}
/**
- * Walks the BLF chunks of a map variant, makes sure it really is a Forge map
- * and reads its name, description and author.
+ * Reads the metadata of a BLF file: name, description, author, file type...
+ *
+ * @return the metadata, or null if it is not a BLF file or a chunk is broken
*/
- private MapInfo readMapInfo(byte[] blf) throws Exception {
+ static ContentInfo readContentInfo(byte[] blf) {
+ if (!isBLFFile(blf)) return null;
+ int[] chunks = findBlfChunks(blf);
+ if (chunks == null) return null;
+
+ ContentInfo info = new ContentInfo();
+ info.hasMapVariant = chunks[1] >= 0;
+ info.complete = chunks[2] >= 0;
+
+ // both chdr and mvar start with the same metadata block, chdr is the official one
+ int metadata = -1;
+ if (hasMetadata(blf, chunks[0])) {
+ metadata = chunks[0] + METADATA_OFFSET;
+ } else if (hasMetadata(blf, chunks[1])) {
+ metadata = chunks[1] + METADATA_OFFSET;
+ }
+ if (metadata >= 0) {
+ info.name = readUtf16(blf, metadata + METADATA_NAME, 32);
+ info.description = readUtf8(blf, metadata + METADATA_DESCRIPTION, 128);
+ info.author = readUtf8(blf, metadata + METADATA_AUTHOR, 16);
+ info.fileType = readInt32(blf, metadata + METADATA_FILE_TYPE);
+ info.mapId = readInt32(blf, metadata + METADATA_MAP_ID);
+ }
+ return info;
+ }
+
+ /**
+ * Makes sure the BLF file really is a complete Forge map and reads its metadata.
+ */
+ private ContentInfo readMapInfo(byte[] blf) throws Exception {
if (!isBLFFile(blf)) {
System.out.println(MAP_VARIANT_FILE + " is not a map variant (BLF) file");
throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": not a Forge map");
}
- int[] chunks = findBlfChunks(blf);
- if (chunks == null) {
+ ContentInfo info = readContentInfo(blf);
+ if (info == null) {
System.out.println("Map variant has a broken chunk");
throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": damaged map variant");
}
- int contentHeader = chunks[0];
- int mapVariant = chunks[1];
- if (chunks[2] < 0) {
+ if (!info.complete) {
System.out.println("Map variant is incomplete, its end of file chunk is missing");
throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": damaged map variant");
}
-
- // both chdr and mvar start with the same metadata block, chdr is the official one
- int metadata = -1;
- if (hasMetadata(blf, contentHeader)) {
- metadata = contentHeader + METADATA_OFFSET;
- } else if (hasMetadata(blf, mapVariant)) {
- metadata = mapVariant + METADATA_OFFSET;
- }
-
- int fileType = metadata >= 0 ? readInt32(blf, metadata + METADATA_FILE_TYPE) : -1;
- if (mapVariant < 0 && fileType != FILE_TYPE_MAP_VARIANT) {
- if (fileType >= 0 && fileType < FILE_TYPE_NAMES.length) {
- System.out.println("This file is a " + FILE_TYPE_NAMES[fileType]);
+ if (!info.hasMapVariant && info.fileType != FILE_TYPE_MAP_VARIANT) {
+ if (info.fileType >= 0 && info.fileType < FILE_TYPE_NAMES.length) {
+ System.out.println("This file is a " + FILE_TYPE_NAMES[info.fileType]);
}
throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS + ": not a Forge map");
}
-
- MapInfo info = new MapInfo();
- if (metadata >= 0) {
- info.name = readUtf16(blf, metadata + METADATA_NAME, 32);
- info.description = readUtf8(blf, metadata + METADATA_DESCRIPTION, 128);
- info.author = readUtf8(blf, metadata + METADATA_AUTHOR, 16);
- info.mapId = readInt32(blf, metadata + METADATA_MAP_ID);
- }
return info;
}
return chunkSize >= METADATA_OFFSET + METADATA_MAP_ID + 4;
}
- /**
- * What the map variant says about itself.
- */
- private static final class MapInfo {
- String name = "";
- String description = "";
- String author = "";
- int mapId = -1;
- }
-
/**
* One entry of the STFS file table.
*/
/*
* Halo 3 Reclaimer [ScreenshotReclaimer.java]
* player data extractor from Xbox 360 version of Halo 3
-* copyright 2025, David Polakovic, licensed under GPLv3
+* copyright 2025, 2026, David Polakovic, licensed under GPLv3
* contact: email@dpolakovic.space
*/
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.LinkedHashMap;
+import java.util.Map;
/**
* Core extraction engine for screenshots.
* <li>Standard RGBA and RGB formats</li>
* </ul>
*
-* @version 1.0
+* @version 1.1
*/
public class ScreenshotReclaimer {
private static final int STFS_MAGIC_CON = 0x434F4E20; // "CON "
private static final int STFS_MAGIC_LIVE = 0x4C495645; // "LIVE"
private static final int STFS_MAGIC_PIRS = 0x50495253; // "PIRS"
-
+
+ // the file inside a package that holds the screenshot and its name
+ private static final String SCREENSHOT_FILE = "screen.shot";
+
/**
* Extracts a screenshot from an Xbox 360 file and saves it as a JPEG.
*
}
System.out.println("Successfully extracted image: " + image.getWidth() + "x" + image.getHeight());
-
- // generate output filename
- String baseName = inputFile.getName();
- int dotIndex = baseName.lastIndexOf('.');
- if (dotIndex > 0) {
- baseName = baseName.substring(0, dotIndex);
- }
- String outputFileName = baseName + "-reclaimed.jpg";
- File outputFile = new File(outputDir, outputFileName);
- if (!ImageIO.write(image, "jpg", outputFile)) {
+ // plain JPEG, named after the screenshot's in-game name like the maps and game modes
+ ByteArrayOutputStream jpeg = new ByteArrayOutputStream();
+ if (!ImageIO.write(image, "jpg", jpeg)) {
throw ErrorHandler.makeException(ErrorHandler.CANT_PROCESS);
}
+ Map<String, byte[]> files = new LinkedHashMap<String, byte[]>();
+ files.put(".jpg", jpeg.toByteArray());
+ Arbiter.saveFiles(outputDir, readScreenshotName(fileData), MapReclaimer.baseName(inputFile), files);
+ }
+
+ /**
+ * The in-game name of the screenshot, read from its package, or "" if unknown.
+ */
+ private String readScreenshotName(byte[] data) {
+ try {
+ byte[] shot = isSTFSFile(data) ? MapReclaimer.readPackageFile(data, SCREENSHOT_FILE) : data;
+ MapReclaimer.ContentInfo info = (shot == null) ? null : MapReclaimer.readContentInfo(shot);
+ if (info != null && !info.name.isEmpty()) {
+ System.out.println("Screenshot: \"" + info.name + "\"");
+ return info.name;
+ }
+ } catch (Exception e) {
+ // the picture is what counts, the name is a bonus
+ }
+ return "";
}
/**
return hasNonBlack && hasNonWhite;
}
-}
\ No newline at end of file
+}
*
* <p>This class contains all the command-line interface logic.</p>
*
-* @version 1.1
+* @version 1.2
*/
public class TerminalUI {
public static void showVersion() {
System.out.println("Halo 3 Reclaimer");
- System.out.println("Version: 1.0");
+ System.out.println("Version: 0.3");
System.out.println("player data extractor from Xbox 360 version of Halo 3");
- System.out.println("copyright 2025, David Polakovic, licensed under GPLv3");
+ System.out.println("copyright 2025, 2026, David Polakovic, licensed under GPLv3");
}
// use Arbiter to process the file or folder
String result = Arbiter.processFile(inputFile, outputDir);
- System.out.println(result + " into: " + outputDir.getAbsolutePath());
+ System.out.println(result + " into: "
+ + new File(outputDir, Arbiter.OUTPUT_DIR).getAbsolutePath());
} catch (Exception e) {
// parse error code from exception message if it starts with a number